package org.garden.id.generator.segment.service;

import org.garden.id.generator.segment.entity.IdConfig;
import org.garden.id.generator.segment.entity.Segment;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.util.concurrent.atomic.AtomicLong;

public class IdConfigService {

    private DataSource dataSource;

    private Integer retry;

    private Integer loadPercent;

    public IdConfigService() {
    }

    public IdConfigService(DataSource dataSource, Integer retry, Integer loadPercent) {
        this.dataSource = dataSource;
        this.retry = retry;
        this.loadPercent = loadPercent;
    }

    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Segment getNextSegment(String type) {
        for (int i = 0; i < retry; i++) {
            IdConfig idConfig = queryByType(type);
            Long newMaxId = idConfig.getMaxId() + idConfig.getStep();
            Long oldMaxId = idConfig.getMaxId();
            int row = updateMaxId(type, idConfig.getId(), newMaxId, oldMaxId, idConfig.getVersion());
            if (row == 1) {
                idConfig.setMaxId(newMaxId);
                return convert(idConfig);
            }
        }
        throw new RuntimeException("error get next segment type:" + type);
    }

    private Segment convert(IdConfig config) {
        Segment segment = new Segment();
        segment.setCurrentId(new AtomicLong(config.getMaxId() - config.getStep()));
        segment.setMaxId(config.getMaxId());
        segment.setDelta(config.getDelta() == null ? 1 : config.getDelta());
        segment.setLoadingId(segment.getCurrentId().get() + config.getStep() * loadPercent / 100);
        return segment;
    }

    private IdConfig queryByType(String type) {
        String sql = "select id, business_type, begin_id, max_id, step, delta, create_time, update_time, version " +
                "from id_config where business_type = ?";
        IdConfig idConfig = new JdbcTemplate(dataSource).queryForObject(sql, new BeanPropertyRowMapper<IdConfig>(IdConfig.class), new Object[]{type});
        if (idConfig == null) {
            throw new RuntimeException("can not find type:" + type);
        }
        return idConfig;
    }

    private int updateMaxId(String type, Long id, Long newMaxId, Long oldMaxId, Long version) {
        String sql = "update id_config set max_id= ?, version=version+1" +
                " where id=? and max_id=? and version=? and business_type=?";
        return new JdbcTemplate(dataSource).update(sql, newMaxId, id, oldMaxId, version, type);
    }

    private boolean create(String type, Integer step, Integer delta) {
        String sql = "insert into id_config (`business_type`, `begin_id`, `max_id`, `step`, `delta`, `version`) " +
                " VALUES (?, 0, 0, ?, ?, 0)";
        return new JdbcTemplate(dataSource).update(sql, type, step == null ? 10000 : step, delta == null ? 1 : delta) > 0;
    }
}
